// 分布式锁管理器

import { BaseManager } from '../core/BaseManager';
import { RedisClient } from '../core/RedisClient';
import { KeyGenerator } from '../utils/keyGenerator';
import { v4 as uuidv4 } from 'uuid';

export interface LockOptions {
  ttl?: number; // 锁的生存时间（秒）
  retryDelay?: number; // 重试延迟（毫秒）
  retryCount?: number; // 重试次数
}

export interface LockInfo {
  token: string;
  owner: string;
  acquiredAt: Date;
  expiresAt: Date;
  count: number | undefined; // 可重入锁的计数
}

export class LockManager extends BaseManager {
  private defaultTtl: number = 30; // 默认30秒
  private defaultRetryDelay: number = 100; // 默认100毫秒
  private defaultRetryCount: number = 3; // 默认重试3次

  constructor(client: RedisClient) {
    super(client, new KeyGenerator({ prefix: 'lock' }));
  }

  /**
   * 获取简单锁
   */
  async acquire(resource: string, options: LockOptions = {}): Promise<string | null> {
    this.validateRequired({ resource });

    const lockKey = this.keyGenerator.lock(resource);
    const token = uuidv4();
    const ttl = options.ttl ?? this.defaultTtl;
    const retryCount = options.retryCount ?? this.defaultRetryCount;
    const retryDelay = options.retryDelay ?? this.defaultRetryDelay;

    const startTime = this.now();

    for (let attempt = 0; attempt <= retryCount; attempt++) {
      try {
        // 尝试获取锁
        const result = await this.execute<string>('set', lockKey, token, 'PX', ttl * 1000, 'NX');

        if (result === 'OK') {
          this.logMetric('lock.acquire', this.now() - startTime, true, {
            resource,
            token,
            ttl,
            attempt,
          });
          return token;
        }

        // 如果不是最后一次尝试，等待后重试
        if (attempt < retryCount) {
          await this.sleep(retryDelay);
        }
      } catch (error) {
        this.logMetric('lock.acquire', this.now() - startTime, false, { resource, error, attempt });

        if (attempt === retryCount) {
          throw this.formatError(error, 'Failed to acquire lock');
        }
      }
    }

    this.logMetric('lock.acquire', this.now() - startTime, false, {
      resource,
      reason: 'max_retries_exceeded',
    });

    return null;
  }

  /**
   * 释放锁
   */
  async release(resource: string, token: string): Promise<boolean> {
    this.validateRequired({ resource, token });

    const lockKey = this.keyGenerator.lock(resource);
    const startTime = this.now();

    try {
      // 使用Lua脚本确保原子性
      const luaScript = `
        if redis.call("get", KEYS[1]) == ARGV[1] then
          return redis.call("del", KEYS[1])
        else
          return 0
        end
      `;

      const result = await this.execute<number>('eval', luaScript, 1, lockKey, token);
      const released = result === 1;

      this.logMetric('lock.release', this.now() - startTime, released, {
        resource,
        token,
      });

      return released;
    } catch (error) {
      this.logMetric('lock.release', this.now() - startTime, false, { resource, token, error });
      throw this.formatError(error, 'Failed to release lock');
    }
  }

  /**
   * 延长锁的生存时间
   */
  async extend(resource: string, token: string, ttl: number): Promise<boolean> {
    this.validateRequired({ resource, token });
    this.validateNumber(ttl, 'ttl', 1);

    const lockKey = this.keyGenerator.lock(resource);
    const startTime = this.now();

    try {
      // 使用Lua脚本确保原子性
      const luaScript = `
        if redis.call("get", KEYS[1]) == ARGV[1] then
          return redis.call("pexpire", KEYS[1], ARGV[2])
        else
          return 0
        end
      `;

      const result = await this.execute<number>('eval', luaScript, 1, lockKey, token, ttl * 1000);
      const extended = result === 1;

      this.logMetric('lock.extend', this.now() - startTime, extended, {
        resource,
        token,
        ttl,
      });

      return extended;
    } catch (error) {
      this.logMetric('lock.extend', this.now() - startTime, false, { resource, token, error });
      throw this.formatError(error, 'Failed to extend lock');
    }
  }

  /**
   * 可重入锁获取
   */
  async acquireReentrant(
    resource: string,
    owner: string,
    options: LockOptions = {}
  ): Promise<boolean> {
    this.validateRequired({ resource, owner });

    const lockKey = this.keyGenerator.generate('lock', resource, 'reentrant');
    const ttl = options.ttl ?? this.defaultTtl;
    const startTime = this.now();

    try {
      // 使用Lua脚本实现可重入锁
      const luaScript = `
        local key = KEYS[1]
        local owner = ARGV[1]
        local ttl = tonumber(ARGV[2])
        
        -- 检查锁是否存在
        local lockInfo = redis.call("hmget", key, "owner", "count")
        local currentOwner = lockInfo[1]
        local count = tonumber(lockInfo[2]) or 0
        
        if currentOwner == false then
          -- 锁不存在，创建新锁
          redis.call("hmset", key, "owner", owner, "count", 1)
          redis.call("expire", key, ttl)
          return 1
        elseif currentOwner == owner then
          -- 同一个所有者，增加计数
          redis.call("hincrby", key, "count", 1)
          redis.call("expire", key, ttl)
          return 1
        else
          -- 锁被其他所有者持有
          return 0
        end
      `;

      const result = await this.execute<number>('eval', luaScript, 1, lockKey, owner, ttl);
      const acquired = result === 1;

      this.logMetric('lock.acquireReentrant', this.now() - startTime, acquired, {
        resource,
        owner,
        ttl,
      });

      return acquired;
    } catch (error) {
      this.logMetric('lock.acquireReentrant', this.now() - startTime, false, {
        resource,
        owner,
        error,
      });
      throw this.formatError(error, 'Failed to acquire reentrant lock');
    }
  }

  /**
   * 可重入锁释放
   */
  async releaseReentrant(resource: string, owner: string): Promise<boolean> {
    this.validateRequired({ resource, owner });

    const lockKey = this.keyGenerator.generate('lock', resource, 'reentrant');
    const startTime = this.now();

    try {
      // 使用Lua脚本实现可重入锁释放
      const luaScript = `
        local key = KEYS[1]
        local owner = ARGV[1]
        
        -- 检查锁信息
        local lockInfo = redis.call("hmget", key, "owner", "count")
        local currentOwner = lockInfo[1]
        local count = tonumber(lockInfo[2]) or 0
        
        if currentOwner ~= owner then
          return 0
        end
        
        if count <= 1 then
          -- 计数为1或更少，删除锁
          redis.call("del", key)
          return 1
        else
          -- 减少计数
          redis.call("hincrby", key, "count", -1)
          return 1
        end
      `;

      const result = await this.execute<number>('eval', luaScript, 1, lockKey, owner);
      const released = result === 1;

      this.logMetric('lock.releaseReentrant', this.now() - startTime, released, {
        resource,
        owner,
      });

      return released;
    } catch (error) {
      this.logMetric('lock.releaseReentrant', this.now() - startTime, false, {
        resource,
        owner,
        error,
      });
      throw this.formatError(error, 'Failed to release reentrant lock');
    }
  }

  /**
   * 获取读锁
   */
  async acquireReadLock(resource: string, options: LockOptions = {}): Promise<string | null> {
    this.validateRequired({ resource });

    const readLockKey = this.keyGenerator.generate('lock', resource, 'read');
    const writeLockKey = this.keyGenerator.generate('lock', resource, 'write');
    const readerId = uuidv4();
    const ttl = options.ttl ?? this.defaultTtl;

    const startTime = this.now();

    try {
      // 使用Lua脚本实现读写锁
      const luaScript = `
        local readKey = KEYS[1]
        local writeKey = KEYS[2]
        local readerId = ARGV[1]
        local ttl = tonumber(ARGV[2])
        
        -- 检查是否有写锁
        if redis.call("exists", writeKey) == 1 then
          return nil
        end
        
        -- 添加读者
        redis.call("sadd", readKey, readerId)
        redis.call("expire", readKey, ttl)
        
        return readerId
      `;

      const result = await this.execute<string | null>(
        'eval',
        luaScript,
        2,
        readLockKey,
        writeLockKey,
        readerId,
        ttl
      );

      this.logMetric('lock.acquireReadLock', this.now() - startTime, result !== null, {
        resource,
        readerId: result,
        ttl,
      });

      return result;
    } catch (error) {
      this.logMetric('lock.acquireReadLock', this.now() - startTime, false, { resource, error });
      throw this.formatError(error, 'Failed to acquire read lock');
    }
  }

  /**
   * 获取写锁
   */
  async acquireWriteLock(resource: string, options: LockOptions = {}): Promise<string | null> {
    this.validateRequired({ resource });

    const readLockKey = this.keyGenerator.generate('lock', resource, 'read');
    const writeLockKey = this.keyGenerator.generate('lock', resource, 'write');
    const writerId = uuidv4();
    const ttl = options.ttl ?? this.defaultTtl;

    const startTime = this.now();

    try {
      // 使用Lua脚本实现写锁获取
      const luaScript = `
        local readKey = KEYS[1]
        local writeKey = KEYS[2]
        local writerId = ARGV[1]
        local ttl = tonumber(ARGV[2])
        
        -- 检查是否有读锁或写锁
        if redis.call("exists", readKey) == 1 or redis.call("exists", writeKey) == 1 then
          return nil
        end
        
        -- 设置写锁
        local result = redis.call("set", writeKey, writerId, "EX", ttl, "NX")
        if result == "OK" then
          return writerId
        else
          return nil
        end
      `;

      const result = await this.execute<string | null>(
        'eval',
        luaScript,
        2,
        readLockKey,
        writeLockKey,
        writerId,
        ttl
      );

      this.logMetric('lock.acquireWriteLock', this.now() - startTime, result !== null, {
        resource,
        writerId: result,
        ttl,
      });

      return result;
    } catch (error) {
      this.logMetric('lock.acquireWriteLock', this.now() - startTime, false, { resource, error });
      throw this.formatError(error, 'Failed to acquire write lock');
    }
  }

  /**
   * 释放读锁
   */
  async releaseReadLock(resource: string, readerId: string): Promise<boolean> {
    this.validateRequired({ resource, readerId });

    const readLockKey = this.keyGenerator.generate('lock', resource, 'read');

    const result = await this.execute<number>('srem', readLockKey, readerId);
    return result === 1;
  }

  /**
   * 释放写锁
   */
  async releaseWriteLock(resource: string, writerId: string): Promise<boolean> {
    this.validateRequired({ resource, writerId });

    const writeLockKey = this.keyGenerator.generate('lock', resource, 'write');

    return this.release(writeLockKey, writerId);
  }

  /**
   * 带回调的锁操作
   */
  async withLock<T>(
    resource: string,
    callback: () => Promise<T>,
    options: LockOptions = {}
  ): Promise<T> {
    this.validateRequired({ resource, callback });

    const token = await this.acquire(resource, options);

    if (!token) {
      throw new Error(`Failed to acquire lock for resource: ${resource}`);
    }

    try {
      return await callback();
    } finally {
      await this.release(resource, token);
    }
  }

  /**
   * 检查锁状态
   */
  async isLocked(resource: string): Promise<boolean> {
    this.validateRequired({ resource });

    const lockKey = this.keyGenerator.lock(resource);
    const exists = await this.keyExists(lockKey);
    return exists > 0;
  }

  /**
   * 获取锁信息
   */
  async getLockInfo(resource: string): Promise<LockInfo | null> {
    this.validateRequired({ resource });

    const lockKey = this.keyGenerator.lock(resource);
    const token = await this.getValue<string>(lockKey);

    if (!token) {
      return null;
    }

    const ttl = await this.getTtl(lockKey);
    const now = new Date();

    return {
      token,
      owner: 'unknown', // 简化实现
      acquiredAt: now, // 简化实现
      expiresAt: new Date(now.getTime() + ttl * 1000),
      count: undefined,
    };
  }

  /**
   * 强制释放锁
   */
  async forceRelease(resource: string): Promise<boolean> {
    this.validateRequired({ resource });

    const lockKey = this.keyGenerator.lock(resource);
    const deleted = await this.deleteKeys(lockKey);
    return deleted > 0;
  }

  /**
   * 清理过期锁
   */
  async cleanup(): Promise<number> {
    const pattern = this.keyGenerator.pattern('*');
    const lockKeys = await this.getKeys(pattern);

    let cleaned = 0;

    for (const lockKey of lockKeys) {
      const ttl = await this.getTtl(lockKey);
      if (ttl === -2) {
        // 键不存在
        cleaned++;
      }
    }

    return cleaned;
  }
}
